package com.just.sxmiaosha.service.impl;


import com.google.common.util.concurrent.RateLimiter;
import com.just.sxmiaosha.cache.StockCache;
import com.just.sxmiaosha.cache.StockUserCache;
import com.just.sxmiaosha.dao.StockDao;
import com.just.sxmiaosha.dao.StockOrderDao;
import com.just.sxmiaosha.dao.StockUserDao;
import com.just.sxmiaosha.entity.Stock;
import com.just.sxmiaosha.entity.StockOrder;
import com.just.sxmiaosha.entity.StockUser;
import com.just.sxmiaosha.service.StockOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author just
 * @since 2020-09-23
 */
@Service
@Slf4j
public class StockOrderServiceImpl implements StockOrderService {

    @Autowired
    private StockDao stockDao;

    @Autowired
    private StockOrderDao stockOrderDao;

    @Autowired
    private StockUserDao stockUserDao;

    @Autowired
    private StockUserCache stockUserCache;

    @Autowired
    private StockCache stockCache;

    RateLimiter rateLimiter = RateLimiter.create(1);

    private static final int DELAY_MILLSECONDS = 1000;

    private static ExecutorService cachedThreadPool = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());

    @Override
    public String createWrongOrder(int sid, int count) {

        //校验库存
        Stock stock = checkStock(sid);
        //扣库存
        reduceStock(sid, count);
        //创建订单
        createOrder(stock);
        return "SUCCESS";
    }


    @Override
    public String createOptimisticOrder(int sid, int count) {

        log.info("等待时间为:{}", rateLimiter.acquire());
        //一定时间再次进来被限流

        if (!rateLimiter.tryAcquire(1000, TimeUnit.MILLISECONDS)) {
            log.warn("你被限流了，真不幸，直接返回失败");
            return "你被限流了，真不幸，直接返回失败";
        }
        //校验库存
        Stock stock = checkStock(sid);

        //更新库存
        int num = stockDao.updateByOptimistic(sid, count, stock.getSale());

        if (num < 1) {
            throw new RuntimeException("过期库存值，更新失败");
        }
        //创建订单
        createOrder(stock);
        int total = stock.getCount() - (stock.getSale() + 1);
        if (total >= 0) {
            return "SUCCESS";
        }
        return "FALSE";
    }

    /***
     * 悲观锁需要和事务一起使用才有效
     * @param sid
     * @param count
     * @return
     */

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public String createPessimisticOrder(int sid, int count) {

        //校验库存(悲观锁for update)
        Stock stock = checkStockForUpdate(sid);
        //更新库存
        int sale = saleStock(count, stock);

        if (sale < 1) {
            return "售卖数据少于1，不创建订单";
        }
        //创建订单
        createOrder(stock);

        return "SUCCESS";
    }

    @Override
    public String getVerifyHash(int sid, int count, int userId) {
        log.info("验证是否在抢购时间内");
        //检查用户是否存在
        StockUser stockUser = stockUserDao.getUser(userId);
        if (stockUser == null) {
            throw new RuntimeException("用户不存在");
        }
        log.info("获取到的用户信息:{}", stockUser.toString());
        //生成hash

        String verify = "SALT" + sid + userId;

        String verifyHash = DigestUtils.md5DigestAsHex(verify.getBytes());

        String hashkey = "stock" + "_" + sid + "_" + userId;
        stockUserCache.setStockUserCache(hashkey, verifyHash);
        log.info("redis写入hashkey:{}", hashkey);
        return verifyHash;
    }

    @Override
    public String createVerifiedOrder(int sid, int count, int userId, String verifyHash) {
        //验证是否在抢购时间
        log.info("验证当前时间是否在抢购时间内");
        //验证hash值合法性
        String hashkey = "stock" + "_" + sid + "_" + userId;
        log.info("入参里的verifyHash:{}", verifyHash);
        String verifyHashInRedis = stockUserCache.getStockUserCache(hashkey);
        log.info("获取到的:verifyHashInRedis:{}", verifyHashInRedis);
        if (!verifyHash.equals(verifyHashInRedis)) {
            throw new RuntimeException("hash值与Redis中不符合");
        }
        //验证用户是否存在
        StockUser stockUser = stockUserDao.getUser(userId);
        if (stockUser == null) {
            throw new RuntimeException("用户不存在");
        }
        //验证商品是否存在
        Stock stock = checkStock(sid);
        if (stock == null) {
            throw new RuntimeException("商品不存在或库存不足");
        }
        //更新库存
        int num = stockDao.updateByOptimistic(sid, count, stock.getSale());

        if (num < 1) {
            throw new RuntimeException("过期库存值，更新失败");
        }
        //创建订单
        createOrder(stock);
        return "SUCCESS";
    }

    @Override
    public String createOrderWithVerifiedUrlAndLimit(int sid, int count, int userId, String verifyHash) {
        stockUserCache.addUserCount(userId);
        int userCount = stockUserCache.getUserCount(userId);
        if (userCount > 10) {
            throw new RuntimeException("该用户被限制了次数");
        }
        String verifiedOrder = createVerifiedOrder(sid, count, userId, verifyHash);
        return verifiedOrder;
    }

    /**
     * 先删缓存再扣数据库
     *
     * @param sid
     * @param count
     * @return
     */
    @Override
    public String createOrderWithCacheV1(int sid, int count) {
        //删缓存
        stockCache.delStockCountCache(sid);

        //扣库存下单
        String result = createPessimisticOrder(sid, count);

        if ("SUCCESS".equals(result)) {
            return "SUCCESS";
        }
        return "FAIL";
    }


    /**
     * 先减库存再删缓存
     *
     * @param sid
     * @param count
     * @return
     */
    @Override
    public String createOrderWithCacheV2(int sid, int count) {
        String result = null;
        try {
            result = createPessimisticOrder(sid, count);
        } catch (Exception e) {
            log.info("创建订单异常 ");
        }

        if ("SUCCESS".equals(result)) {
            stockCache.delStockCountCache(sid);
            log.info("删除缓存");
        }

        return "SUCCESS";
    }


    /**
     * 先删除缓存，再设置延时时间后再次删除
     * @param sid
     * @param count
     * @return
     */
    @Override
    public String createOrderWithCacheV3(int sid, int count) {
        //删缓存
        stockCache.delStockCountCache(sid);
        //扣库存下单
        String result = createPessimisticOrder(sid, count);
        cachedThreadPool.execute(new delCacheByThread(sid));
        if("SUCCESS".equals(result)){
            return "SUCCESS";
        }else {
            return "FAIL";
        }
    }

    /**
     * 异步下订单
     * @param sid
     * @param count
     * @return
     */
    @Override
    public String createOrderWithMq(int sid, int count) {
        Integer num = stockCache.getStockCountCache(sid);
        if (num == 0) {
            return "秒杀请求失败，库存不足.....";
        }
        //去写mq
        return null;
    }

    /***
     * 多线程此处锁不住 需加事务
     * @param count
     * @param stock
     * @return
     */
    private int saleStock(int count, Stock stock) {
        int sale = stock.getSale() + count;
        stockDao.updateSaleStock(stock.getId(), sale);
        return sale;

    }

    /**
     * forupdate
     *
     * @param sid
     * @return
     */
    private Stock checkStockForUpdate(int sid) {
        log.info("悲观锁校验库存");
        Stock stock = stockDao.getStockByIdForUpdate(sid);
        if (stock.getSale().equals(stock.getCount())) {
            throw new RuntimeException("库存不足");
        }
        return stock;
    }


    private Stock createOrder(Stock stock) {
        log.info("创建订单,入参:{}", stock);
        stockOrderDao.insertOrder(stock.getName(), stock.getId());
        log.info("创建订单完成");
        return stock;
    }


    private void reduceStock(int sid, int count) {
        log.info("减库存,入参:{},{}", sid, count);
        stockDao.updateStock(sid, count);
    }


    private Stock checkStock(int sid) {
        log.info("校验库存,入参:{}", sid);
        Stock stock = stockDao.getStockCountById(sid);
        if (stock.getCount().equals(stock.getSale())) {
            throw new RuntimeException("库存不足");
        }
        log.info("校验库存,出参:{}", stock);
        return stock;
    }


    private class delCacheByThread implements Runnable {
        private int sid;

        public delCacheByThread(int sid) {
            this.sid = sid;
        }

        @Override
        public void run() {
            try {
                log.info("异步执行缓存再删除，商品id：[{}]， 首先休眠：[{}] 毫秒", sid, DELAY_MILLSECONDS);
                Thread.sleep(DELAY_MILLSECONDS);
                stockCache.delStockCountCache(sid);
                log.info("再次删除商品id：[{}] 缓存", sid);
            } catch (InterruptedException e) {
                log.info("异常");
                e.printStackTrace();
            }
        }
    }
}
